package com.jay.mypracticesdemo

import android.os.Build
import android.text.TextUtils
import androidx.annotation.RequiresApi
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.security.Key
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.NoSuchAlgorithmException
import java.security.PrivateKey
import java.security.PublicKey
import java.security.Signature
import java.security.spec.InvalidKeySpecException
import java.security.spec.KeySpec
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util.Base64
import java.util.concurrent.ConcurrentHashMap
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.NoSuchPaddingException
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

/**
 * 支持AES、DES、RSA加密、数字签名以及生成对称密钥和非对称密钥对
 */
@RequiresApi(Build.VERSION_CODES.O)
object CryptoUtils {
    private val DEFAULT_CHARSET: Charset = StandardCharsets.UTF_8
    private val BASE64_ENCODER: Base64.Encoder = Base64.getEncoder()
    private val BASE64_DECODER: Base64.Decoder = Base64.getDecoder()

    private val KEY_FACTORY_CACHE: MutableMap<Algorithm, KeyFactory?> = ConcurrentHashMap()
    private val CIPHER_CACHE: MutableMap<Algorithm, Cipher?> = HashMap()

    /**
     * 生成对称密钥，目前支持的算法有AES、DES
     * @param algorithm
     * @return
     * @throws NoSuchAlgorithmException
     */
    @Throws(NoSuchAlgorithmException::class)
    fun generateSymmetricKey(algorithm: Algorithm): String {
        val generator = KeyGenerator.getInstance(algorithm.name)
        generator.init(algorithm.keySize)
        val secretKey = generator.generateKey()
        return BASE64_ENCODER.encodeToString(secretKey.encoded)
    }

    /**
     * 生成非对称密钥对，目前支持的算法有RSA、DSA。备注：默认生成的密钥格式为PKCS8
     * @param algorithm
     * @return
     * @throws NoSuchAlgorithmException
     */
    @Throws(NoSuchAlgorithmException::class)
    fun generateAsymmetricKeyPair(algorithm: Algorithm): AsymmetricKeyPair {
        val generator = KeyPairGenerator.getInstance(algorithm.name)
        generator.initialize(algorithm.keySize)
        val keyPair = generator.generateKeyPair()
        val publicKey = BASE64_ENCODER.encodeToString(keyPair.public.encoded)
        val privateKey = BASE64_ENCODER.encodeToString(keyPair.private.encoded)
        return AsymmetricKeyPair(publicKey, privateKey)
    }

    @Throws(Exception::class)
    fun encryptByRSA(publicKeyText: String?, plainText: String): String {
        return encryptAsymmetrically(publicKeyText, plainText, Encryption.RSA_ECB_PKCS1)
    }

    @Throws(Exception::class)
    fun decryptByRSA(privateKeyText: String?, ciphertext: String?): String {
        return decryptAsymmetrically(privateKeyText, ciphertext, Encryption.RSA_ECB_PKCS1)
    }

    /**
     * SHA1签名算法和DSA加密算法结合使用生成数字签名
     * @param privateKeyText
     * @param msg
     * @return 数字签名
     * @throws Exception
     */
    @Throws(Exception::class)
    fun signBySHA1WithDSA(privateKeyText: String?, msg: String): String {
        return doSign(privateKeyText, msg, Encryption.DSA, Signing.SHA1WithDSA)
    }

    /**
     * SHA1签名算法和RSA加密算法结合使用生成数字签名
     * @param privateKeyText 私钥
     * @param msg 待加签内容
     * @return 数字签名
     * @throws Exception
     */
    @Throws(Exception::class)
    fun signBySHA1WithRSA(privateKeyText: String?, msg: String): String {
        return doSign(privateKeyText, msg, Encryption.RSA_ECB_PKCS1, Signing.SHA1WithRSA)
    }

    /**
     * SHA256签名算法和RSA加密算法结合使用生成数字签名
     * @param privateKeyText 私钥
     * @param msg 待加签内容
     * @return 数字签名
     * @throws Exception
     */
    @Throws(Exception::class)
    fun signBySHA256WithRSA(privateKeyText: String?, msg: String): String {
        return doSign(privateKeyText, msg, Encryption.RSA_ECB_PKCS1, Signing.SHA256WithRSA)
    }

    /**
     * SHA1签名算法和DSA加密算法检验数字签名
     * @param publicKeyText 公钥
     * @param msg 待验签内容
     * @param signatureText 数字
     * @return 检验是否成功
     * @throws Exception
     */
    @Throws(Exception::class)
    fun verifyBySHA1WithDSA(publicKeyText: String?, msg: String, signatureText: String?): Boolean {
        return doVerify(publicKeyText, msg, signatureText, Encryption.DSA, Signing.SHA1WithDSA)
    }

    /**
     * SHA1签名算法和RSA加密算法检验数字签名
     * @param publicKeyText 公钥
     * @param msg 待验签内容
     * @param signatureText 签名
     * @return 校验是否成功
     * @throws Exception
     */
    @Throws(Exception::class)
    fun verifyBySHA1WithRSA(publicKeyText: String?, msg: String, signatureText: String?): Boolean {
        return doVerify(publicKeyText, msg, signatureText, Encryption.RSA_ECB_PKCS1, Signing.SHA1WithRSA)
    }

    /**
     * SHA256签名算法和RSA加密算法检验数字签名
     * @param publicKeyText 公钥
     * @param msg 待验签内容
     * @param signatureText 签名
     * @return 校验是否成功
     * @throws Exception
     */
    @Throws(Exception::class)
    fun verifyBySHA256WithRSA(publicKeyText: String?, msg: String, signatureText: String?): Boolean {
        return doVerify(publicKeyText, msg, signatureText, Encryption.RSA_ECB_PKCS1, Signing.SHA256WithRSA)
    }

    /**
     * 对称加密
     * @param secretKey 密钥
     * @param iv 加密向量，只有CBC模式才支持，如果是CBC则必传
     * @param plainText 明文
     * @param algorithm 对称加密算法，如AES、DES
     * @return
     * @throws Exception
     */
    @Throws(Exception::class)
    fun encryptSymmetrically(secretKey: String, iv: String, plainText: String, algorithm: Algorithm): String {
        val key = decodeSymmetricKey(secretKey, algorithm)
        val ivParameterSpec = if (TextUtils.isEmpty(iv)) null else decodeIv(iv)
        val plainTextInBytes = plainText.toByteArray(DEFAULT_CHARSET)
        val ciphertextInBytes = transform(algorithm, Cipher.ENCRYPT_MODE, key, ivParameterSpec, plainTextInBytes)

        return BASE64_ENCODER.encodeToString(ciphertextInBytes)
    }

    /**
     * 对称解密
     * @param secretKey 密钥
     * @param iv 加密向量，只有CBC模式才支持，如果是CBC则必传
     * @param ciphertext 密文
     * @param algorithm 对称加密算法，如AES、DES
     * @return
     * @throws Exception
     */
    @Throws(Exception::class)
    fun decryptSymmetrically(secretKey: String, iv: String, ciphertext: String, algorithm: Algorithm): String {
        val key = decodeSymmetricKey(secretKey, algorithm)
        val ivParameterSpec = if (iv.isEmpty()) null else decodeIv(iv)
        val ciphertextInBytes = BASE64_DECODER.decode(ciphertext)
        val plainTextInBytes = transform(algorithm, Cipher.DECRYPT_MODE, key, ivParameterSpec, ciphertextInBytes)
        return String(plainTextInBytes, DEFAULT_CHARSET)
    }

    /**
     * 非对称加密
     * @param publicKeyText 公钥
     * @param plainText 明文
     * @param algorithm 非对称加密算法
     * @return
     * @throws Exception
     */
    @Throws(Exception::class)
    fun encryptAsymmetrically(publicKeyText: String?, plainText: String, algorithm: Algorithm): String {
        val publicKey = regeneratePublicKey(publicKeyText, algorithm)
        val plainTextInBytes = plainText.toByteArray(DEFAULT_CHARSET)
        val ciphertextInBytes = transform(algorithm, Cipher.ENCRYPT_MODE, publicKey, plainTextInBytes)
        return BASE64_ENCODER.encodeToString(ciphertextInBytes)
    }

    /**
     * 非对称解密
     * @param privateKeyText 私钥
     * @param ciphertext 密文
     * @param algorithm 非对称加密算法
     * @return
     * @throws Exception
     */
    @Throws(Exception::class)
    fun decryptAsymmetrically(privateKeyText: String?, ciphertext: String?, algorithm: Algorithm): String {
        val privateKey = regeneratePrivateKey(privateKeyText, algorithm)
        val ciphertextInBytes = BASE64_DECODER.decode(ciphertext)
        val plainTextInBytes = transform(algorithm, Cipher.DECRYPT_MODE, privateKey, ciphertextInBytes)
        return String(plainTextInBytes, DEFAULT_CHARSET)
    }

    /**
     * 生成数字签名
     * @param privateKeyText 私钥
     * @param msg 传输的数据
     * @param encryptionAlgorithm 加密算法，见Algorithm中的加密算法
     * @param signatureAlgorithm 签名算法，见Algorithm中的签名算法
     * @return 数字签名
     * @throws Exception
     */
    @Throws(Exception::class)
    fun doSign(privateKeyText: String?, msg: String, encryptionAlgorithm: Algorithm, signatureAlgorithm: Algorithm): String {
        val privateKey = regeneratePrivateKey(privateKeyText, encryptionAlgorithm)
        // Signature只支持签名算法
        val signature = Signature.getInstance(signatureAlgorithm.name)
        signature.initSign(privateKey)
        signature.update(msg.toByteArray(DEFAULT_CHARSET))
        val signatureInBytes = signature.sign()
        return BASE64_ENCODER.encodeToString(signatureInBytes)
    }

    /**
     * 数字签名验证
     * @param publicKeyText 公钥
     * @param msg 传输的数据
     * @param signatureText 数字签名
     * @param encryptionAlgorithm 加密算法，见Algorithm中的加密算法
     * @param signatureAlgorithm 签名算法，见Algorithm中的签名算法
     * @return 校验是否成功
     * @throws Exception
     */
    @Throws(Exception::class)
    fun doVerify(
        publicKeyText: String?, msg: String, signatureText: String?, encryptionAlgorithm: Algorithm,
        signatureAlgorithm: Algorithm
    ): Boolean {
        val publicKey = regeneratePublicKey(publicKeyText, encryptionAlgorithm)
        val signature = Signature.getInstance(signatureAlgorithm.name)
        signature.initVerify(publicKey)
        signature.update(msg.toByteArray(DEFAULT_CHARSET))
        return signature.verify(BASE64_DECODER.decode(signatureText))
    }

    /**
     * 将密钥进行Base64位解码，重新生成SecretKey实例
     * @param secretKey 密钥
     * @param algorithm 算法
     * @return
     */
    private fun decodeSymmetricKey(secretKey: String, algorithm: Algorithm): SecretKey {
//        val key = BASE64_DECODER.decode(secretKey)
        val key = secretKey.toByteArray(Charsets.UTF_8)
        return SecretKeySpec(key, algorithm.name)
    }

    private fun decodeIv(iv: String): IvParameterSpec {
//        val ivInBytes = BASE64_DECODER.decode(iv)
//        println(ivInBytes.size)
        return IvParameterSpec(iv.toByteArray(Charsets.UTF_8))
    }

    @Throws(NoSuchAlgorithmException::class, InvalidKeySpecException::class)
    private fun regeneratePublicKey(publicKeyText: String?, algorithm: Algorithm): PublicKey {
        val keyInBytes = BASE64_DECODER.decode(publicKeyText)
        val keyFactory = getKeyFactory(algorithm)
        // 公钥必须使用RSAPublicKeySpec或者X509EncodedKeySpec
        val publicKeySpec: KeySpec = X509EncodedKeySpec(keyInBytes)
        val publicKey = keyFactory!!.generatePublic(publicKeySpec)
        return publicKey
    }

    @Throws(Exception::class)
    private fun regeneratePrivateKey(key: String?, algorithm: Algorithm): PrivateKey {
        val keyInBytes = BASE64_DECODER.decode(key)
        val keyFactory = getKeyFactory(algorithm)
        // 私钥必须使用RSAPrivateCrtKeySpec或者PKCS8EncodedKeySpec
        val privateKeySpec: KeySpec = PKCS8EncodedKeySpec(keyInBytes)
        val privateKey = keyFactory!!.generatePrivate(privateKeySpec)
        return privateKey
    }

    @Throws(NoSuchAlgorithmException::class)
    private fun getKeyFactory(algorithm: Algorithm): KeyFactory? {
        var keyFactory = KEY_FACTORY_CACHE[algorithm]
        if (keyFactory == null) {
            keyFactory = KeyFactory.getInstance(algorithm.name)
            KEY_FACTORY_CACHE[algorithm] = keyFactory
        }

        return keyFactory
    }

    @Throws(Exception::class)
    private fun transform(algorithm: Algorithm, mode: Int, key: Key, msg: ByteArray): ByteArray {
        return transform(algorithm, mode, key, null, msg)
    }

    @Throws(Exception::class)
    private fun transform(algorithm: Algorithm, mode: Int, key: Key, iv: IvParameterSpec?, msg: ByteArray): ByteArray {
        var cipher = CIPHER_CACHE[algorithm]
        // double check，减少上下文切换
        if (cipher == null) {
            synchronized(CryptoUtils::class.java) {
                if ((CIPHER_CACHE[algorithm].also { cipher = it }) == null) {
                    cipher = determineWhichCipherToUse(algorithm)
                    CIPHER_CACHE[algorithm] = cipher
                }
                cipher!!.init(mode, key, iv)
                return cipher!!.doFinal(msg)
            }
        }

        synchronized(CryptoUtils::class.java) {
            cipher!!.init(mode, key, iv)
            return cipher!!.doFinal(msg)
        }
    }

    @Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class)
    private fun determineWhichCipherToUse(algorithm: Algorithm): Cipher {
        val cipher: Cipher
        val transformation: String? = algorithm.transformation
        // 官方推荐的transformation使用algorithm/mode/padding组合，SunJCE使用ECB作为默认模式，使用PKCS5Padding作为默认填充
        cipher = if (transformation?.isEmpty() == false) {
            Cipher.getInstance(transformation)
        } else {
            Cipher.getInstance(algorithm.name)
        }

        return cipher
    }

    interface Encryption {
        companion object {
            val AES_ECB_PKCS5: Algorithm = Algorithm("AES", "AES/ECB/PKCS5Padding", 128)
            val AES_CBC_PKCS5: Algorithm = Algorithm("AES", "AES/CBC/PKCS5Padding", 128)
            val DES_ECB_PKCS5: Algorithm = Algorithm("DES", "DES/ECB/PKCS5Padding", 56)
            val DES_CBC_PKCS5: Algorithm = Algorithm("DES", "DES/CBC/PKCS5Padding", 56)
            val RSA_ECB_PKCS1: Algorithm = Algorithm("RSA", "RSA/ECB/PKCS1Padding", 1024)
            val DSA: Algorithm = Algorithm("DSA", 1024)
        }
    }

    interface Signing {
        companion object {
            val SHA1WithDSA: Algorithm = Algorithm("SHA1withDSA", 1024)
            val SHA1WithRSA: Algorithm = Algorithm("SHA1WithRSA", 2048)
            val SHA256WithRSA: Algorithm = Algorithm("SHA256WithRSA", 2048)
        }
    }

    /**
     * 算法分为加密算法和签名算法，更多算法实现见：<br></br>
     * [jdk8中的标准算法](https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#impl)
     */
    data class Algorithm(val name: String?, val transformation: String?, val keySize: Int) {
        constructor(name: String?, keySize: Int) : this(name, null, keySize)
    }

    data class AsymmetricKeyPair(
        private val publicKey: String? = null,
        private val privateKey: String? = null
    )
}


